/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds.ui;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.GraphicsEnvironment;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JColorChooser;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
import jsyntaxpane.syntaxkits.LuaSyntaxKit;
import net.worlds.AssetManager;
import net.worlds.WorldsException;
import net.worlds.WorldsSettings;
import net.worlds.Main;
import net.worlds.ObjectList;
import net.worlds.PropertyDef;
import net.worlds.RedoRunnable;
import net.worlds.UndoRunnable;
import net.worlds.Util;
import net.worlds.component.ConnectToServerDialog;
import net.worlds.component.ImportWorldsFrame;
import net.worlds.component.SaveModelDialog;
import net.worlds.component.ScriptEditor;
import net.worlds.component.StartServerDialog;
import net.worlds.object.Box;
import net.worlds.object.Camera;
import net.worlds.object.GameObject;
import net.worlds.object.Joint;
import net.worlds.object.Model;
import net.worlds.object.Part;
import net.worlds.object.Prop;
import net.worlds.object.Script;
import net.worlds.object.Tool;
import net.worlds.object.Triangle;
import org.jbox2d.common.Vec2D;
import org.jbox2d.dynamics.Body;
import se.krka.kahlua.vm.LuaTableImpl;


public class UI {
    public static int propClass = PropertyDef.CLASS_BASIC;
    
    public static boolean viewVisible = false;
    public static boolean mouseOverUI = false;
    public static boolean mouseClickUI = false;
    public static boolean repaintUI = false;
    public static boolean repaintAdvancedTools = false;
    public static boolean menuOpen = false;
    public static boolean advancedToolsVisible = false;

    private static ScriptEditor scriptEditor =  null;

    private static HashMap<Integer, Runnable> controlKeyShortcuts = new HashMap<Integer, Runnable>(16);

    private static BufferedImage currentMouseImage = null;
    private static BufferedImage mouseImage = null;
    public static boolean mouseInside = true;
    public static final Cursor defaultCursor = Cursor.getDefaultCursor();
    public static final Cursor crosshairCursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
    public static Cursor cursor = defaultCursor;
    
    public static boolean camMouseDragEnabled = true;
    private static final List<GameObject> selected = Collections.synchronizedList(new ArrayList<GameObject>(WorldsSettings.MAX_OBJECTS));
    private static ArrayList<GameObject> clipboard = new ArrayList<GameObject>(WorldsSettings.MAX_OBJECTS);
    private static ArrayList<Body> dragBodies = new ArrayList<Body>(WorldsSettings.MAX_OBJECTS);
    private static Part mainDragPart;

    public static final int MODE_BOX = 0;
    public static final int MODE_ARROW = 1;
    public static final int MODE_NONE = 2;
    public static final int MODE_CIRCLE = 3;
    public static final int MODE_HINGE = 4;
    public static final int MODE_ROTATE = 5;
    public static final int MODE_WELD = 6;
    public static final int MODE_DELETE = 7;
    public static final int MODE_GRAB = 8;
    public static final int MODE_WATER = 9;
    public static final int MODE_LADDER = 10;
    public static final int MODE_SCALE = 11;
    public static final int MODE_SEAT = 12;
    public static final int MODE_TERRAIN = 13;
    public static final int MODE_ROPE = 14;
    public static final int MODE_TRIANGLE = 15;

    public static int oldMouseMode = 2;

    public static int mouseMode = 1;

    public static boolean guiEnabled = true;

    public static boolean drawingBox = false;
    public static boolean drawingCircle = false;
    public static boolean drawingTriangle = false;
    private static Color nextObjectColor;
    private static Color[] defaultObjectColor = new Color[16];
    public static final Color waterColor = new Color(80, 120, 160);
    public static final Color ladderColor = new Color(170, 120, 60);

    public static boolean shiftPressed = false;

    public static Box scaleBox;
    public static Vec2D scaleVert;
    public static Vec2D scaleOppositeVert;

    public static Script editScript;

    public static boolean showGrid = false;
    public static boolean snapToGrid = false;

    public static boolean showConsole = true;

    public static boolean drawOver = false;
    
    public static final int UNDO_SIZE = 1;
    private static List<GameObject> lastDeleted;
    private static GameObject rootParent;
    private static ArrayList<UndoAction> undo = new ArrayList<UndoAction>(UNDO_SIZE);
    private static ArrayList<UndoAction> redo = new ArrayList<UndoAction>(UNDO_SIZE);
    private static JFrame colorPickerFrame;

    public static void select(GameObject obj)
    {
        synchronized(selected)
        {
            if(!selected.contains(obj))
                selected.add(obj);
            obj.selected = true;
            if(Main.isEditor())
            {
                Main.getEditorTop().duplicateItem.setEnabled(true);
                Main.getEditorTop().copyItem.setEnabled(true);
                Main.getEditorTop().cutItem.setEnabled(true);
                Main.getEditorTop().groupItem.setEnabled(true);
                Main.getEditorTop().bottomFrame.setVisible(true);
                UI.repaintAdvancedTools = true;
                if(selected.size() == 1 && selected.get(0) instanceof Part)
                {
                    Main.getEditorTop().hingeItem.setEnabled(true);
                    Main.getEditorTop().moveBackItem.setEnabled(true);
                    Main.getEditorTop().moveFrontItem.setEnabled(true);
                }
                else
                    Main.getEditorTop().hingeItem.setEnabled(false);
            }
        }
    }
    public static void selectContainedJoints()
    {
        for(GameObject j : Main.getGame().getCurrentWorld().getJoints())
        {
            if(selected.contains(((Joint)j).getPart1()) && selected.contains(((Joint)j).getPart2()))
                UI.select(j);
        }
    }
    public static void setMainDragPart(Part p)
    {
        mainDragPart = p;
    }
    public static void addDragPart(Vec2D mouse, Part p) throws WorldsException
    {
        if(!(Main.isClient() && p.inUse))
        {
            if(!dragBodies.contains(p.getBody()))
            {
                p.getBody().beforeDragPosition = p.getBody().getPosition();
                setDistanceFromMouse(mouse, p.getBody());
                dragBodies.add(p.getBody());
                for(Part part : p.getBody().getParts())
                {
                    p.setInUse(true);
                }
                p.getBody().setNoCollide(true);
                //p.setTempFixed(true);
            }
        }
    }
    /*private static void addDragParts(Model m) throws BuildismException
{
        for(GameObject c : m.getChildren())
        {
            if(c instanceof Part && !dragParts.contains(c) && !c.inUse)
            {
                dragParts.add((Part) c);
                ((Part)c).setTempFixed(true);
                c.setInUse(true);
                setDistanceFromMouse((Part)c);
            }
            else if(c instanceof Model)
            {
                addDragParts((Model) c);
            }
        }
    }*/
    private static void setDistanceFromMouse(Vec2D mouse, Body b)
    {
        Vec2D pos = new Vec2D(b.getPosition());
        b.setDistanceFromMouse(new Vec2D(pos.x - mouse.x, pos.y - mouse.y));
    }
    public static void clearDragParts() throws WorldsException
    {
        if(dragBodies.size() == 0)
            return;
        final Object[] dragBodiesArray = dragBodies.toArray();
        boolean go = false;
        for(Object o : dragBodiesArray)
        {
            Body b = (Body) o;
            if(!b.getPosition().equals(b.beforeDragPosition))
            {
                go = true;
            }
            if(b.getParts().length == 0)
                continue;
            b.setNoCollide(false);
            for(Part p : b.parts)
            {
                p.setInUse(false);
            }
        }
        if(go)
            UI.action(new RedoRunnable()
            {
                @Override
                public Object run(boolean redo) throws WorldsException {
                    for(Object o : dragBodiesArray)
                    {
                        Body b = (Body) o;
                        if(redo)
                            b.setXForm(b.afterDragPosition, b.getAngle());
                        else
                            b.afterDragPosition = b.getPosition();
                        b.getParts()[0].setProp("Position", b.getParts()[0].getPosition(), true);
                        b.wakeUp();
                    }
                    return null;
                }
            }, new UndoRunnable()
            {

                @Override
                public void run(Object obj) throws WorldsException {
                    for(Object o : dragBodiesArray)
                    {
                        Body b = (Body) o;
                        b.setXForm(b.beforeDragPosition, b.getAngle());
                        b.wakeUp();
                    }
                }
            }, false);
        dragBodies.clear();
    }
    public static void processDragParts(Vec2D pos) throws WorldsException
    {
        for(Body b : dragBodies)
        {
            Vec2D mouse = b.getDistanceFromMouse();
            /*if(UI.snapToGrid)
            {
                if(mouse.x > pos.x)
                {
                    pos.x = Math.floor(pos.x);
                    mouse.x = Math.ceil(mouse.x);
                }
                else
                {
                    pos.x = Math.ceil(pos.x);
                    mouse.x = Math.floor(mouse.x);
                }
                if(mouse.y > pos.y)
                {
                    pos.y = Math.ceil(pos.y);
                    mouse.y = Math.floor(mouse.y);
                }
                else
                {
                    pos.y = Math.floor(pos.y);
                    mouse.y = Math.ceil(mouse.y);
                }
            }*/
            Vec2D bodyPos = pos.add(mouse);
            //System.err.println(bodyPos);
            if(UI.snapToGrid)
            {
                Part p = mainDragPart;
                if(p instanceof Box || p instanceof Triangle)
                {
                    if((int) p.getVec2DProp("Size").x % 2 != 0)
                        bodyPos.x = (float) (Math.floor(bodyPos.x) + 0.5);
                    else
                        bodyPos.x = Math.round(bodyPos.x);
                    if((int) p.getVec2DProp("Size").y % 2 != 0)
                        bodyPos.y = (float) (Math.floor(bodyPos.y) + 0.5);
                    else
                        bodyPos.y = Math.round(bodyPos.y);
                }
                else
                    bodyPos = bodyPos.round();
            }
            b.setXForm(bodyPos, b.getAngle());
        }
    }
    public static ArrayList<Body> getDragBodies()
    {
        return dragBodies;
    }
    public static boolean isDraggingPart()
    {
        return dragBodies.size() > 0;
    }
    public static void clearSelection(boolean isAuto) throws WorldsException
    {
        synchronized(selected)
        {
            for(final GameObject obj : selected)
            {
                obj.selected = false;
            }
        }
        Main.processTempTasks();
        if(!isAuto)
        {
            SwingUtilities.invokeLater(new Runnable()
            {

                public void run() {
                    ((PartsTreeSelectionModel)Main.getApplet().getPartsTree().getSelectionModel()).clearSelection(true);
                }

            });
        }
        synchronized(selected)
        {
            selected.clear();
        }
        if(Main.isEditor())
        {
            Main.getEditorTop().hingeItem.setEnabled(false);
            Main.getEditorTop().moveBackItem.setEnabled(false);
            Main.getEditorTop().moveFrontItem.setEnabled(false);
            Main.getEditorTop().copyItem.setEnabled(false);
            Main.getEditorTop().duplicateItem.setEnabled(false);
            Main.getEditorTop().bottomFrame.setVisible(false);
            UI.repaintAdvancedTools = true;
            Main.getEditorTop().cutItem.setEnabled(false);
            Main.getEditorTop().groupItem.setEnabled(false);
        }

    }
    public static void updatePropsTable() throws WorldsException
    {
        PropsTableCellEditor.cellEditing = false;
        synchronized(selected)
        {
            GameObject[] array = new GameObject[selected.size()];
            selected.toArray(array);
            Main.getApplet().getPropsTable().setObject(array);
        }
    }
    public static void clearAdvancedTools()
    {
        selected.clear();
        if(Main.isEditor())
            Main.getEditorTop().scriptingFrame.reload();
        try {
            Main.getApplet().getPropsTable().setObject(new GameObject[]{});
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }

    }
    public static void doPropChange(final String key, final Object value)
    {
        final Object[] selectedObjects = selected.toArray();
        final HashMap<GameObject, Object> oldValues = new HashMap<GameObject, Object>(selectedObjects.length);
        //this is called from the AWT thread!
        UI.action(new RedoRunnable()
        {
            @Override
            public Object run(boolean redo) throws WorldsException {
                for(Object o : selectedObjects)
                {
                    GameObject obj = (GameObject) o;
                    Object oldValue = obj.getProp(key);
                    oldValues.put(obj, oldValue);
                    try
                    {
                        if(!ObjectList.getDef(obj.getType()).visible && key.equals("Name"))
                        {
                            Main.log("Unable to change name of "+obj);
                        }
                        else
                        {
                            obj.setProp(key, value, true);
                        }
                    }
                    catch(WorldsException ex)
                    {
                        if(oldValue == null)
                            oldValue = "";
                        Main.getApplet().getPropsTable().changeKeyTo(key, oldValue.toString());
                    }
                }
                return null;
            }
        }, new UndoRunnable()
        {

            @Override
            public void run(Object n) throws WorldsException {
                for(Object o : selectedObjects)
                {
                    GameObject obj = (GameObject) o;
                    obj.setProp(key, oldValues.get(obj), true);
                }
            }
        }, true);
    }
    public static void deselect(GameObject obj)
    {
        synchronized(selected)
        {
            selected.remove(obj);
            obj.selected = false;
            if(Main.isEditor())
            {
                if(selected.isEmpty())
                {
                    Main.getEditorTop().copyItem.setEnabled(false);
                    Main.getEditorTop().duplicateItem.setEnabled(false);
                    Main.getEditorTop().bottomFrame.setVisible(false);
                    UI.repaintAdvancedTools = true;
                    Main.getEditorTop().cutItem.setEnabled(false);
                    Main.getEditorTop().groupItem.setEnabled(false);
                }
                if(selected.size() == 1 && selected.get(0) instanceof Part)
                {
                    Main.getEditorTop().moveBackItem.setEnabled(true);
                    Main.getEditorTop().moveFrontItem.setEnabled(true);
                    Main.getEditorTop().hingeItem.setEnabled(true);
                }
                else
                {
                    Main.getEditorTop().moveBackItem.setEnabled(false);
                    Main.getEditorTop().moveFrontItem.setEnabled(false);
                    Main.getEditorTop().hingeItem.setEnabled(false);
                }
            }
        }
    }
    public static List<GameObject> getSelected()
    {
        return selected;
    }
    public static int getNumberSelected()
    {
        return selected.size();
    }
    public static void updateSelection() throws WorldsException
    {
        if(Main.noView)
            return;
        final TreePath[] paths = new TreePath[getNumberSelected()];
        LuaTableImpl tbl = new LuaTableImpl();
        synchronized(selected)
        {
            for(int i = 0; i < selected.size(); i++)
            {
                paths[i] = new TreePath(selected.get(i).getTreeNode().getPath());
                tbl.rawset(i+1, selected.get(i));
            }
        }
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                Main.getApplet().getPartsTree().setSelectionPaths(paths, true);
            }
        });
        updatePropsTable();
        if(Main.isEditor())
            Main.getEditorTop().scriptingFrame.reload();
        Main.getGame().getEditorObject().getEvent("SelectionChanged").fire(new Object[]{tbl});
    }
   
    public static void deleteSelection() throws WorldsException
    {
        UI.action(new RedoRunnable()
        {
            @Override
            public Object run(boolean redo) throws WorldsException {
                List<GameObject> source = !redo ? selected : lastDeleted;
                List<GameObject> removedObjects = UI.copy(source);
                synchronized(source)
                {
                    for (GameObject p : source) {
                        if(!ObjectList.getDef(p.getType()).visible)
                            Main.log("Unable to remove "+p);
                        else
                            p.remove();
                    }
                }
                clearSelection(false);
                updateSelection();
                return removedObjects;
            }
        }, new UndoRunnable()
        {

            @Override
            public void run(Object objects) throws WorldsException {
                lastDeleted = UI.paste((List<GameObject>)objects, true, false, true);
            }
        }, true);
    }
    public static void onChildAdded(final GameObject p, final GameObject c)
    {
        if(!Main.noView && guiEnabled)
        {
            Runnable r = new Runnable()
            {
                public void run()
                {
                    try {
                        if(Main.getGame() == null || Main.getGame().getCurrentWorld() == null)
                                return;
                        if ((Main.getGame() != null && Main.getGame().getCurrentWorld() != null && (p.world != Main.getGame().getCurrentWorld() || c.world != Main.getGame().getCurrentWorld())) || c.getParent() != p || !c.isInWorld() || !c.explorable || !c.getParent().explorable) {
                            return;
                        }
                        if(p.getTreeNode() != null && c.getTreeNode() != null && c.getTreeNode().getParent() == p.getTreeNode())
                        {
                            return;
                        }
                    } catch (WorldsException ex) {
                        ex.printStackTrace();
                    }
                    PartTreeNode newNode = c.getTreeNode();
                    p.getTreeNode().add(newNode);
                    Main.getApplet().getPartsTree().scrollPathToVisible(new TreePath(newNode.getPath()));
                }
            };
            if(Thread.currentThread() == Main.mainThread)
                SwingUtilities.invokeLater(r);
            else
                r.run();
        }
    }
    public static void onChildRemoved(final GameObject p, final GameObject c)
    {
        if(guiEnabled && c.isInWorld() && c.getTreeNode().getParent() != null)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    if(!Main.noView && guiEnabled && p.isInWorld() && c.getTreeNode().getParent() != null)
                    {
                                                if(p.world.game != Main.getGame())
                            return;
                        int idx = p.getTreeNode().getIndex(c.getTreeNode());
                        p.getTreeNode().remove(c.getTreeNode());
                        Main.getApplet().getPartsTree().getModel().nodesWereRemoved(p.getTreeNode(), new int[]{idx}, new Object[]{c.getTreeNode()});
                    }
                }
            });
        }
    }
    public static void groupSelection() throws WorldsException
    {
        if(selected.isEmpty())
            return;
        else if(selected.size() == 1 && selected.get(0) instanceof Model)
            try
            {
                final Model m = (Model) selected.get(0);
                final GameObject parent = m.getParent();
                deselect(m);
                List<GameObject> children = m.getChildren();
                synchronized(children)
                {
                    for(final GameObject c : Util.copyList(children))
                    {
                        c.setParent(parent);
                        select(c);
                    }
                    Main.processTempTasks();
                }
                m.remove();
                updateSelection();
            }
            catch(WorldsException ex)
            {
                Main.error(ex);
            }
        else
            try
            {
                Model m = (Model) Main.getGame().getCurrentWorld().createObject("Model");
                synchronized(selected)
                {
                    m.setParent(selected.get(0).getParent());
                    for(GameObject obj : selected)
                    {
                        if(!selected.contains(obj.getParent()))
                            obj.setParent(m);
                    }
                }
                select(m);
                updateSelection();
            }
            catch(WorldsException ex)
            {
                Main.error(ex);
            }
    }
    public static void setNextObjectColor(Color c)
    {
        nextObjectColor = c;
    }
    public static void setDefaltObjectColor(Color c, int m)
    {
        defaultObjectColor[m] = c;
    }
    public static Color getNextObjectColor()
    {
        if(defaultObjectColor[mouseMode] != null)
            return defaultObjectColor[mouseMode];
        else
            return nextObjectColor;
    }
    public static void editScript(final Script s, final String params) throws WorldsException
    {
        if(s.inUse)
            return;
        if(scriptEditor == null)
            scriptEditor = new ScriptEditor(s, params);
        else
        {
            scriptEditor.setScript(s);
            scriptEditor.setParams(params);
        }
        Main.getApplet().setGameView(scriptEditor);
        editScript = s;
        Main.runInMainThread(new Runnable()
        {
            public void run() {
                s.setInUse(true);
            }
        });
    }
    public static void editScript(final Script s) throws WorldsException
    {
        if(s.inUse)
            return;
        if(scriptEditor == null)
            scriptEditor = new ScriptEditor(s);
        else
            scriptEditor.setScript(s);
        Main.getApplet().setGameView(scriptEditor);
        editScript = s;
        Main.runInMainThread(new Runnable()
        {
            public void run() {
                s.setInUse(true);
            }
        });
    }
    public static void closeScript()
    {
        final Script s = editScript;
        Main.runInMainThread(new Runnable()
        {
            public void run() {
                s.setInUse(false);
            }
        });
        if(Main.isEditor())
            Main.getEditorTop().scriptingFrame.reload();
        Main.getApplet().setGameView(null);
        scriptEditor = null;
    }
    public static boolean canCopy()
    {
        return !selected.isEmpty();
    }
    public static boolean canPaste()
    {
        return !clipboard.isEmpty();
    }
    public static void doCopy()
    {
        Runnable r = new Runnable()
        {
            public void run() {
                if(selected.isEmpty())
                    return;
                clipboard.clear();
                clipboard = copy(selected);
                if(Main.isEditor())
                {
                    Main.getEditorTop().pasteItem.setEnabled(true);
                }
            }
        };
        if(Thread.currentThread() == Main.mainThread)
            r.run();
        else
            Main.runInMainThread(r);
    }
    private static ArrayList<GameObject> copy(List<GameObject> source)
    {
        ArrayList<GameObject> copies = new ArrayList<GameObject>(source.size());
        synchronized(source)
        {
            for(GameObject obj : source)
            {
                if(!(obj instanceof Joint))
                    copies.add((GameObject)obj);
            }
        }
        for(GameObject obj : (ArrayList<GameObject>) copies.clone())
        {
            try {
                if (!copies.contains(obj.getParent())) {
                    rootParent = obj.getParent();
                }
            } catch (WorldsException ex) {
                throw new RuntimeException(ex);
            }
        }
        for(GameObject obj : (ArrayList<GameObject>) copies.clone())
        {
            copies.remove(obj);
            copies.add((GameObject) obj.clone());
        }
        return copies;
    }
    public static void doPaste(final boolean pasteInto) throws WorldsException
    {
        Runnable r = new Runnable()
        {
            public void run() {
                try {
                    paste(clipboard, pasteInto, true, false);
                } catch (WorldsException ex) {
                    throw new RuntimeException(ex);
                }
            }
        };
        if(Thread.currentThread() == Main.mainThread)
            r.run();
        else
            Main.runInMainThread(r);
    }
    public static List<GameObject> paste(List<GameObject> source, boolean pasteInto, boolean select, boolean useOldParent) throws WorldsException
    {
        final ArrayList<GameObject> clones = new ArrayList<GameObject>(source.size());
        for(GameObject obj : source)
        {
            clones.add((GameObject) obj.clone());
        }
        for (GameObject obj : clones) {
            obj.setID(Main.getGame().getFreeID());
            if((obj.getParent() == null || !clones.contains(obj.getParent())) && (selected.size() == 1 || useOldParent) && pasteInto)
                obj.setProp("Parent", useOldParent ? rootParent : UI.selected.get(0), true);
            else if((obj.getParent() == null || !clones.contains(obj.getParent())))
                obj.setProp("Parent", Main.getGame().getCurrentWorld().getService("World"), true);
            else
                obj.setProp("Parent", obj.getObjectProp("Parent"), true);
        }
        if(select)
            clearSelection(true);
        for (GameObject obj : clones) {
            if(select)
                select(obj);
            obj.isSendable = true;
            if(obj instanceof Model)
            {
                ((Model)obj).makeJoints();
                ((Model)obj).clearJointDefs();
            }
            if(Main.isServer())
            {
                Main.getServer().sendObject(obj);
                Main.getServer().sendObjectRecursive(obj);
            }
            else if(Main.isClient())
            {
                Main.getClient().sendObject(obj);
                Main.getClient().sendObjectRecursive(obj);
            }
        }
        if(select)
            updateSelection();
        return clones;
    }
    public static void startServer()
    {
        new StartServerDialog().setVisible(true);
    }
    public static void startClient()
    {
        new ConnectToServerDialog().setVisible(true);
    }
    public static void doSave()
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new GameFileFilter());
        int result = chooser.showSaveDialog(null);
        if(result == JFileChooser.APPROVE_OPTION)
        {
            String path = chooser.getSelectedFile().getPath();
            if(!path.endsWith(".worlds"))
                path += ".worlds";
            final String finalPath = path;
            try
            {
                Main.getGame().serialize(new FileOutputStream(finalPath), false);
            } catch (Exception ex) {
                ex.printStackTrace();
                    throw new RuntimeException(ex);
            }
        }
        System.out.println("Save complete");
    }
    public static void doSaveModelOnline()
    {
        String data = null;
        if(getNumberSelected() == 0)
        {
            Main.log("Please select 1 object only (try making a group)");
        }
        else try {
            synchronized(selected)
            {
                if (selected.get(0).getParent() == Main.getGame().getCurrentWorld().getRootObject()) {
                    Main.log("Please do not select a service object");
                } else {
                    GameObject obj = selected.get(0);
                    Writer w = new StringWriter();
                    w.write("--"+obj.getType()+"\n");
                    w.write("--"+obj.getID()+"\n");
                    w.write("function c(p) return create(p) end\ngame:scheduleTask(function()\n");
                    Main.getGame().getCurrentWorld().resetSerializedBodies();
                    Main.getGame().getCurrentWorld().serializeObjectRecursive(w, obj, true, false);
                    ArrayList<Part> parts = Main.getGame().getCurrentWorld().getSerializedParts();
                    ArrayList<Joint> joints = new ArrayList<Joint>(WorldsSettings.MAX_OBJECTS);
                    for(Part p : parts)
                    {
                        for(Joint j : p.getJoints())
                        {
                            if(!joints.contains(j))
                            {
                                Main.getGame().getCurrentWorld().serializeObject(w, j, false);
                                joints.add(j);
                            }
                        }
                    }
                    w.write("end)");
                    w.close();
                    data = w.toString();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
        if(data != null)
        {
            SaveModelDialog d = new SaveModelDialog(data);
            d.setVisible(true);
        }
    }
    public static void doSaveModel()
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new ModelFileFilter());
        int result = chooser.showSaveDialog(null);
        GameObject obj;
        if(getNumberSelected() == 0)
        {
            Main.log("Please select 1 object only (try making a group)");
            return;
        }
        else try {
            synchronized(selected)
            {
                if (selected.get(0).getParent() == Main.getGame().getCurrentWorld().getRootObject()) {
                    Main.log("Please do not select a service object");
                    return;
                } else {
                    obj = selected.get(0);
                }
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
            return;
        }
        if(result == JFileChooser.APPROVE_OPTION)
        {
            try {
                String path = chooser.getSelectedFile().getPath();
                if(!path.endsWith(".wmodel"))
                    path += ".wmodel";
                Writer w = new OutputStreamWriter(new FileOutputStream(path));
                w.write("--"+obj.getType()+"\n");
                w.write("--"+obj.getID()+"\n");
                w.write("function c(p) return create(p) end\ngame:scheduleTask(function()\n");
                Main.getGame().getCurrentWorld().resetSerializedBodies();
                Main.getGame().getCurrentWorld().serializeObjectRecursive(w, obj, true, false);
                ArrayList<Part> parts = Main.getGame().getCurrentWorld().getSerializedParts();
                ArrayList<Joint> joints = new ArrayList<Joint>(WorldsSettings.MAX_OBJECTS);
                for(Part p : parts)
                {
                    for(Joint j : p.getJoints())
                    {
                        if(!joints.contains(j))
                        {
                            Main.getGame().getCurrentWorld().serializeObject(w, j, false);
                            joints.add(j);
                        }
                    }
                }
                w.write("end)");
                w.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    public static String saveToURL(int gameID, String url) throws IOException, WorldsException
    {
        String data = "uid="+Main.getLoginID()+"&key="+Main.getLoginKey()+"&game="+gameID+"&data=";
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(data.getBytes());
        Main.getGame().serialize(out, true);

        out.write(new String("&image=").getBytes());

        BufferedImage img = Main.getImage();
        ByteArrayOutputStream imgOut = new ByteArrayOutputStream();
        ImageIO.write(img, "JPEG", imgOut);

        sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();
        String b64 = enc.encode(imgOut.toByteArray());

        out.write(URLEncoder.encode(b64, "UTF-8").getBytes());

        HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
        c.setRequestMethod("POST");
        c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        c.setRequestProperty("Content-Language", "en-US");
        c.setRequestProperty("Content-Length", ""+out.size());
        c.setDoInput(true);
        c.setDoOutput(true);
        c.connect();
        
        c.getOutputStream().write(out.toByteArray());
        
        return new BufferedReader(new InputStreamReader(c.getInputStream())).readLine();
    }
    public static void doLoad()
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new GameFileFilter());
        int result = chooser.showOpenDialog(null);
        if(result == JFileChooser.APPROVE_OPTION)
        {
            try
            {
                String path = chooser.getSelectedFile().getPath();
                Main.loadFromFile(path);
            } catch (Exception ex) {
            ex.printStackTrace();
            }
        }
    }
    public static void doImportWorlds()
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new GameFileFilter());
        int result = chooser.showOpenDialog(null);
        if(result == JFileChooser.APPROVE_OPTION)
        {
            try
            {
                String path = chooser.getSelectedFile().getPath();
                ImportWorldsFrame f = new ImportWorldsFrame();
                f.showWorlds(path);
                f.setVisible(true);
            } catch (Exception ex) {
            ex.printStackTrace();
            }
        }
    }
    public static void doLoadModel()
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new ModelFileFilter());
        int result = chooser.showOpenDialog(null);
        if(result == JFileChooser.APPROVE_OPTION)
        {
            try
            {
                String path = chooser.getSelectedFile().getPath();
                Main.loadModelFromFile(path);
            } catch (Exception ex) {
            ex.printStackTrace();
            }
        }
    }
    public static void hideTools()
    {
        Main.getApplet().hideTools();
    }
    public static void makeJoint(final Vec2D worldVec) throws WorldsException
    {
        int type;
        if(mouseMode == MODE_WELD)
            type = 0;
        else
            type = 1;
        if(Main.isBuildClient())
            Main.getClient().sendAddJoint(worldVec, type);
        else
        {
            Part p1 = null;
            Part p2 = null;
            for(int i = 2; i >= 0; i--)
            {
                List<GameObject> l = Main.getGame().getCurrentWorld().getLayer(i);
                synchronized(l)
                {
                    for(int j = l.size() - 1; j >= 0; j--)
                    {
                        GameObject obj = l.get(j);
                        if(!obj.disabled && obj instanceof Part && !(obj instanceof Tool) && ((Part)obj).getShape().testPoint(((Part)obj).getBody().getXForm(), worldVec))
                        {
                            if(p1 == null)
                                p1 = (Part) obj;
                            else if(p2 == null)
                                p2 = (Part) obj;
                        }
                    }
                }
            }
            if(p1 == null || p2 == null || (p1.world != p2.world))
                return;
            /*else if(type == 0 && p1.getBody() == p2.getBody())
                return;*/
            final Part finalp1 = p1;
            final Part finalp2 = p2;
            final int mm = mouseMode;
            UI.action(new RedoRunnable()
            {

                @Override
                public Object run(boolean redo) throws WorldsException {
                    Joint j;
                    if(mm == UI.MODE_WELD)
                        j = (Joint) Main.getGame().getCurrentWorld().createObject("Weld");
                    else
                        j = (Joint) Main.getGame().getCurrentWorld().createObject("Hinge"); 
                    j.setProp("Anchor", worldVec, true);
                    j.setProp("Part1", finalp1, true);
                    j.setProp("Part2", finalp2, true);
                    j.setProp("Parent", Main.getGame().getCurrentWorld().getService("JointsService"), true);
                    return j;
                }
            }, new UndoRunnable()
            {

                @Override
                public void run(Object obj) throws WorldsException {
                    ((Joint)obj).remove();
                }
            }, true);
        }
    }
    public static void moveToFront(final Prop p, final boolean replicate) throws WorldsException
    {
        try {
            List<GameObject> layer = Main.getGame().getCurrentWorld().getLayerOriginal(((GameObject) p).getIntProp("Layer"));
            synchronized(layer)
            {
                layer.remove((GameObject) p);
                layer.add((GameObject) p);
            }
            ((GameObject) p).getParent().removeChild((GameObject) p);
            ((GameObject) p).getParent().addChild((GameObject) p);
            if(!replicate)
                return;
            if(Main.isServer())
            {
                Main.getServer().sendMoveTo((Part)p, 1);
            }
            else if(Main.isClient())
            {
                Main.getClient().sendMoveTo((Part)p, 1);
            }
        } catch (WorldsException ex) {
            throw new RuntimeException(ex);
        }
    }
    public static void moveToFront(Prop p) throws WorldsException
    {
        moveToFront(p, true);
    }
    public static void moveToBack(final Prop p, final boolean replicate)
    {
        try {
            List<GameObject> layer = Main.getGame().getCurrentWorld().getLayerOriginal(((GameObject) p).getIntProp("Layer"));
            synchronized(layer)
            {
                layer.remove((GameObject) p);
                layer.add(0, (GameObject) p);
            }
            ((GameObject) p).getParent().removeChild((GameObject) p);
            ((GameObject) p).getParent().insertChildAt((GameObject) p, 0);
        } catch (WorldsException ex) {
            throw new RuntimeException(ex);
        }
        if(!replicate)
            return;
        if(Main.isServer())
        {
            Main.getServer().sendMoveTo((Part)p, 0);
        }
        else if(Main.isClient())
        {
            Main.getClient().sendMoveTo((Part)p, 0);
        }
    }
    public static void moveToBack(Prop p) throws WorldsException
    {
        moveToBack(p, true);
    }
    public static void resetCamera()
    {
        Camera c = null;
        try {
            c = (Camera) Main.getGame().currentCamera;
            if(c == null)
                return;
            c.zoomToCenter();
            c.setProp("ScaleFactor", 8.0f, true);
        } catch (WorldsException ex) {
            System.err.println("Error setting camera position");
        }
    }

    public static void setMouseImage(String name)
    {
        if(name == null)
        {
            if(!UI.mouseOverUI)
                currentMouseImage = null;
            mouseImage = null;
        }
        else
        {
            try {
                mouseImage = AssetManager.loadImage(name, "resource://" + name + ".png");
                if(!UI.mouseOverUI)
                    currentMouseImage = mouseImage;
            } catch (IOException ex) {
                currentMouseImage = null;
                mouseImage = null;
                throw new RuntimeException("Unknown mouse image "+name);
            }
        }
    }

    public static void mouseOverUI()
    {
        currentMouseImage = null;
    }

    public static void mouseOffUI()
    {
        currentMouseImage = mouseImage;
    }

    public static BufferedImage getMouseImage()
    {
        return currentMouseImage;
    }

    public static void clearMouseMode()
    {
        if(Main.noView)
            return;
        if(Main.isEditor())
            mouseMode = oldMouseMode;
        else
            mouseMode = MODE_NONE;
        setMouseImage(null);
        setCursor(Cursor.getDefaultCursor());
    }

    public static void setCursor(Cursor c)
    {
        cursor = c;
        Main.setCursor(cursor);
    }

    public static void resetCursor()
    {
        if(!Main.noView)
            setCursor(Cursor.getDefaultCursor());
    }

    public static void moveToLayer(int l)
    {
        synchronized(selected)
        {
            for(GameObject obj : selected)
            {
                if(obj instanceof Prop)
                    try {
                    obj.setProp("Layer", l, true);
                } catch (WorldsException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    public static void addKeyboardShortcut(int k, Runnable a)
    {
        controlKeyShortcuts.put(k, a);
    }

    public static Runnable getKeyboardShortcut(int k)
    {
        return controlKeyShortcuts.get(k);
    }

    public static void setMouseMode(int m)
    {
        mouseMode = m;
        oldMouseMode = m;
        if(Main.getGame() != null)
            Main.getGame().setMouseMode(m);
    }
    
    public static void action(final RedoRunnable action, final UndoRunnable undo, boolean mainThread)
    {
        redo.clear();
        final UndoAction undoAction = new UndoAction();
        Runnable a;
        if(mainThread)
            a = new Runnable()
            {
                @Override
                public void run() {
                    Main.runInMainThread(new Runnable()
                    {

                        @Override
                        public void run() {
                            try {
                                undoAction.obj = action.run(!undoAction.initial);
                                undoAction.initial = false;
                            } catch (WorldsException ex) {
                                ex.printStackTrace();
                            }
                        }
                        
                    });
                }
            };
        else
            a = new Runnable()
            {
                @Override
                public void run() {
                    try {
                        undoAction.obj = action.run(!undoAction.initial);
                        undoAction.initial = false;
                    } catch (WorldsException ex) {
                        ex.printStackTrace();
                    }
                }
            };
        Runnable u;
        if(mainThread)
            u = new Runnable()
            {
                @Override
                public void run() {
                    Main.runInMainThread(new Runnable()
                    {

                        @Override
                        public void run() {
                    try {
                        undo.run(undoAction.obj);
                    } catch (WorldsException ex) {
                        ex.printStackTrace();
                    }
                        }
                        
                    });
                }
            };
        else
            u = new Runnable()
            {
                @Override
                public void run() {
                    try {
                        undo.run(undoAction.obj);
                    } catch (WorldsException ex) {
                        ex.printStackTrace();
                    }
                }
            };
        a.run();
        
        undoAction.action = a;
        undoAction.undoAction = u;
        UI.undo.add(undoAction);
        if(UI.undo.size() == UI.UNDO_SIZE + 1)
        {
            UI.undo.remove(0);
        }
        if(Main.isEditor())
            Main.getEditorTop().showUndoButton();
    }
    
    public static int undoRedo()
    {
        if(!undo.isEmpty())
        {
            undo();
            return 0;
        }
        else if(!redo.isEmpty())
        {
            redo();
            return 1;
        }
        else
            return -1;
    }
    
    public static void undo()
    {
        if(undo.isEmpty())
            return;
        UndoAction r = undo.remove(undo.size() - 1);
        r.undoAction.run();
        redo.add(r);
    }
    
    public static void redo()
    {
        if(redo.isEmpty())
            return;
        UndoAction r = redo.remove(redo.size() - 1);
        r.action.run();
        undo.add(r);
    }
    
    public static void clearUndoList()
    {
        undo.clear();
        redo.clear();
        lastDeleted = null;
        rootParent = null;
        if(Main.getEditorTop() != null)
            Main.getEditorTop().hideUndoButton();
    }
    
    private static class UndoAction {
        public Runnable action;
        public Runnable undoAction;
        public Object obj;
        public boolean initial = true;
    }
    
    public static void showDefaultColorPicker()
    {
        final int m = mouseMode;
        final JColorChooser chooser = new JColorChooser();
        if(defaultObjectColor == null)
            chooser.setColor(Color.white);
        else
            chooser.setColor(defaultObjectColor[m]);
        chooser.setPreviewPanel(new JPanel());
        if(colorPickerFrame != null)
            colorPickerFrame.dispose();
        colorPickerFrame = new JFrame("Select Color");
        try
        {
            colorPickerFrame.setIconImages(Util.icons);
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        colorPickerFrame.addWindowListener(new WindowListener()
        {

            public void windowOpened(WindowEvent e) {

            }

            public void windowClosing(WindowEvent e) {
                setDefaltObjectColor(chooser.getColor(), m);
                colorPickerFrame = null;
            }

            public void windowClosed(WindowEvent e) {

            }

            public void windowIconified(WindowEvent e) {

            }

            public void windowDeiconified(WindowEvent e) {

            }

            public void windowActivated(WindowEvent e) {

            }

            public void windowDeactivated(WindowEvent e) {

            }
        });
        colorPickerFrame.add(chooser);
        colorPickerFrame.pack();
        colorPickerFrame.setLocationRelativeTo(Main.getApplet());
        colorPickerFrame.setResizable(false);
        colorPickerFrame.setVisible(true);
    }
    
    static {
        if(!GraphicsEnvironment.isHeadless())
            LuaSyntaxKit.initKit();
    }
}
